// ShadowMapping.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"


#include <iostream>
#include <map>

// GLEW
#define GLEW_STATIC
#include <gl/glew.h>
// GLFW
#include <GLFW/glfw3.h>
// Shader
#include "../common/shaderwrapper.h"
// Camera
#include "../common/Camera.h"
// Model
#include "../common/Model.h"
// SOIL
#include <SOIL.h>
// GLM
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>


void error_callback (int error, const char* description);
void key_callback (GLFWwindow* window, int key, int scancode, int action, int mode);
void mouse_callback (GLFWwindow* window, double xpos, double ypos);
void scroll_callback (GLFWwindow* window, double xoffset, double yoffset);
void do_movement ();
GLuint loadTexture (GLchar* path, GLboolean alpha = GL_FALSE);
GLuint loadCubemap (std::vector<const GLchar*> faces);
void RenderScene (Shader &shader);
void RenderCube ();
void RenderQuad ();

const GLuint WIDTH = 800, HEIGHT = 600;
GLfloat lastX = 400, lastY = 300;

bool keys[1024];
GLfloat deltaTime = 0.0f;	// Time between current frame and last frame
GLfloat lastFrame = 0.0f;	// Time of last frame

							// Camera
Camera camera (glm::vec3 (0.0f, 0.0f, 3.0f));

// Global variables
GLuint woodTexture;
GLuint planeVAO;

int main ()
{
	glfwInit ();
	glfwSetErrorCallback (error_callback);
	glfwWindowHint (GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint (GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint (GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	glfwWindowHint (GLFW_RESIZABLE, GL_FALSE);

	GLFWwindow* window = glfwCreateWindow (WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
	if (window == nullptr) {
		std::cout << "Failed to create GLFW window." << std::endl;
		glfwTerminate ();
		return -1;
	}
	glfwMakeContextCurrent (window);
	glfwSetInputMode (window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

	glewExperimental = GL_TRUE;
	if (glewInit () != GLEW_OK) {
		std::cout << "Failed to initialize GLEW" << std::endl;
		glfwTerminate ();
		return -1;
	}

	glViewport (0, 0, WIDTH, HEIGHT);

	glfwSetKeyCallback (window, key_callback);
	glfwSetCursorPosCallback (window, mouse_callback);
	glfwSetScrollCallback (window, scroll_callback);

	glEnable (GL_DEPTH_TEST);

#pragma region "shader & textures & models "

	Shader debugDepthQuad ("debugDepthQuad.vtx", "debugDepthQuad.frag"/*, "obj.geo"*/);
	Shader simpleDepthShader ("simple_depth.vtx", "simple_depth.frag");
	Shader shader ("obj.vtx", "obj.frag");

#pragma endregion


#pragma region "object_initialization"

	GLfloat planeVertices[] = {
		// Positions          // Normals         // Texture Coords
		25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 0.0f,
		-25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 25.0f,
		-25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,

		25.0f, -0.5f, 25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 0.0f,
		25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f, 25.0f, 25.0f,
		-25.0f, -0.5f, -25.0f, 0.0f, 1.0f, 0.0f, 0.0f, 25.0f
	};
	// Setup plane VAO
	GLuint planeVBO;
	glGenVertexArrays (1, &planeVAO);
	glGenBuffers (1, &planeVBO);
	glBindVertexArray (planeVAO);
	glBindBuffer (GL_ARRAY_BUFFER, planeVBO);
	glBufferData (GL_ARRAY_BUFFER, sizeof (planeVertices), &planeVertices, GL_STATIC_DRAW);
	glEnableVertexAttribArray (0);
	glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof (GLfloat), (GLvoid*)0);
	glEnableVertexAttribArray (1);
	glVertexAttribPointer (1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof (GLfloat), (GLvoid*)(3 * sizeof (GLfloat)));
	glEnableVertexAttribArray (2);
	glVertexAttribPointer (2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof (GLfloat), (GLvoid*)(6 * sizeof (GLfloat)));
	glBindVertexArray (0);

	// Light source
	glm::vec3 lightPos (-2.0f, 4.0f, -1.0f);

	// Load textures
	woodTexture = loadTexture ("../Resource/container.jpg");

	// Configure depth map FBO
	GLuint depthMapFBO;
	glGenFramebuffers (1, &depthMapFBO);
	// Create a 2D texture that we'll use as the framebuffer's depth buffer
	const GLuint SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024;
	GLuint depthMap;
	glGenTextures (1, &depthMap);
	glBindTexture (GL_TEXTURE_2D, depthMap);
	glTexImage2D (GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
		SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
	GLfloat borderColor[] = { 1.0, 1.0, 1.0, 1.0 };
	glTexParameterfv (GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
	
	// Attach the generated depth texture as the framebuffer's depth buffer
	glBindFramebuffer (GL_FRAMEBUFFER, depthMapFBO);
	glFramebufferTexture2D (GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0);
	glDrawBuffer (GL_NONE);
	glReadBuffer (GL_NONE);
	glBindFramebuffer (GL_FRAMEBUFFER, 0);

	glClearColor (0.1f, 0.1f, 0.1f, 1.0f);

#pragma endregion

	while (!glfwWindowShouldClose (window)) {
		// Check and call events
		glfwPollEvents ();

		GLfloat currentFrame = glfwGetTime ();
		deltaTime = currentFrame - lastFrame;
		lastFrame = currentFrame;

		do_movement ();

		// 1. first render to depth map
		// - Get light projection/view matrix
		GLfloat near_plane = 1.0f, far_plane = 7.5f;
		glm::mat4 lightProjection = glm::ortho (-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane);
		glm::mat4 lightView = glm::lookAt (
			lightPos,
			glm::vec3 (0.0f, 0.0f, 0.0f),
			glm::vec3 (0.0f, 1.0f, 0.0f));
		glm::mat4 lightSpaceMatrix = lightProjection * lightView;
		// - Render scene from light's point of view
		simpleDepthShader.Use ();
		GLint lightSpaceMatrixLocation = glGetUniformLocation (simpleDepthShader.Program, "lightSpaceMatrix");
		glUniformMatrix4fv (lightSpaceMatrixLocation, 1, GL_FALSE, glm::value_ptr (lightSpaceMatrix));

		glViewport (0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
		glBindFramebuffer (GL_FRAMEBUFFER, depthMapFBO);
		{
			glClear (GL_DEPTH_BUFFER_BIT);
			RenderScene (simpleDepthShader);
		}
		glBindFramebuffer (GL_FRAMEBUFFER, 0);

		// 2. then render scene as normal with shadow mapping (using depth map)
		glViewport (0, 0, WIDTH, HEIGHT);
		glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		shader.Use ();
		GLint projMatrixLoc = glGetUniformLocation (shader.Program, "projection");
		GLint viewMatrixLoc = glGetUniformLocation (shader.Program, "view");
		GLint lightSpaceLoc = glGetUniformLocation (shader.Program, "lightSpaceMatrix");
		GLint diffuseTexLoc = glGetUniformLocation (shader.Program, "diffuseTexture");
		GLint shadowMapLoc = glGetUniformLocation (shader.Program, "shadowMap");
		GLint lightPosLoc = glGetUniformLocation (shader.Program, "lightPos");
		GLint viewPosLoc = glGetUniformLocation (shader.Program, "viewPos");

		glm::mat4 projection = glm::perspective (glm::radians (camera.zoom), (float)WIDTH / (float)HEIGHT, 0.1f, 350.0f);
		glUniformMatrix4fv (projMatrixLoc, 1, GL_FALSE, glm::value_ptr (projection));
		glUniformMatrix4fv (viewMatrixLoc, 1, GL_FALSE, glm::value_ptr (camera.GetViewMatrix ()));
		glUniformMatrix4fv (lightSpaceLoc, 1, GL_FALSE, glm::value_ptr (lightSpaceMatrix));
		glActiveTexture (GL_TEXTURE0);
		glBindTexture (GL_TEXTURE_2D, woodTexture);
		glUniform1i (diffuseTexLoc, 0);
		glActiveTexture (GL_TEXTURE1);
		glBindTexture (GL_TEXTURE_2D, depthMap);
		glUniform1i (shadowMapLoc, 1);
		glUniform3fv (lightPosLoc, 1, glm::value_ptr (lightPos));
		glUniform3fv (viewPosLoc, 1, glm::value_ptr (camera.position));
		RenderScene (shader);

		// - Render depth map to quad
		debugDepthQuad.Use ();
		//glUniform1f (glGetUniformLocation (debugDepthQuad.Program, "near_plane"), near_plane);
		//glUniform1f (glGetUniformLocation (debugDepthQuad.Program, "far_plane"), far_plane);
		glActiveTexture (GL_TEXTURE0);
		glBindTexture (GL_TEXTURE_2D, depthMap);
		//RenderQuad ();

		glfwSwapBuffers (window);
	}

	// free VAOs,VBOs,TEXTUREs
	// ...

	glfwTerminate ();
	return 0;
}

// This function loads a texture from file. Note: texture loading functions like these are usually 
// managed by a 'Resource Manager' that manages all resources (like textures, models, audio). 
// For learning purposes we'll just define it as a utility function.
GLuint loadTexture (GLchar* path, GLboolean alpha)
{
	auto soil_load_opt = alpha ? SOIL_LOAD_RGBA : SOIL_LOAD_RGB;
	auto gl_texture_opt = alpha ? GL_RGBA : GL_RGB;

	//Generate texture ID and load texture data 
	GLuint textureID;
	glGenTextures (1, &textureID);
	int width, height;
	unsigned char* image = SOIL_load_image (path, &width, &height, 0, soil_load_opt);
	// Assign texture to ID
	glBindTexture (GL_TEXTURE_2D, textureID);
	glTexImage2D (GL_TEXTURE_2D, 0, gl_texture_opt, width, height, 0, gl_texture_opt, GL_UNSIGNED_BYTE, image);
	glGenerateMipmap (GL_TEXTURE_2D);

	// Parameters
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glBindTexture (GL_TEXTURE_2D, 0);
	SOIL_free_image_data (image);
	return textureID;
}

GLuint loadCubemap (std::vector<const GLchar*> faces) {
	GLuint textureID;
	glGenTextures (1, &textureID);
	glActiveTexture (GL_TEXTURE0);

	int width, height;
	unsigned char* image;

	glBindTexture (GL_TEXTURE_CUBE_MAP, textureID);
	for (GLuint i = 0; i < faces.size (); i++) {
		image = SOIL_load_image (faces[i], &width, &height, 0, SOIL_LOAD_AUTO);
		glTexImage2D (
			GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0,
			GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image
			);
		SOIL_free_image_data (image);
	}
	glTexParameteri (GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri (GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri (GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri (GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri (GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
	glBindTexture (GL_TEXTURE_CUBE_MAP, 0);

	return textureID;
}

void RenderScene (Shader &shader)
{
	// Floor
	glm::mat4 model;
	glUniformMatrix4fv (glGetUniformLocation (shader.Program, "model"), 1, GL_FALSE, glm::value_ptr (model));
	glBindVertexArray (planeVAO);
	glDrawArrays (GL_TRIANGLES, 0, 6);
	glBindVertexArray (0);
	// Cubes
	model = glm::mat4 ();
	model = glm::translate (model, glm::vec3 (0.0f, 1.5f, 0.0));
	glUniformMatrix4fv (glGetUniformLocation (shader.Program, "model"), 1, GL_FALSE, glm::value_ptr (model));
	RenderCube ();
	model = glm::mat4 ();
	model = glm::translate (model, glm::vec3 (2.0f, 0.0f, 1.0));
	glUniformMatrix4fv (glGetUniformLocation (shader.Program, "model"), 1, GL_FALSE, glm::value_ptr (model));
	RenderCube ();
	model = glm::mat4 ();
	model = glm::translate (model, glm::vec3 (-1.0f, 0.0f, 2.0));
	model = glm::rotate (model, 60.0f, glm::normalize (glm::vec3 (1.0, 0.0, 1.0)));
	model = glm::scale (model, glm::vec3 (0.5));
	glUniformMatrix4fv (glGetUniformLocation (shader.Program, "model"), 1, GL_FALSE, glm::value_ptr (model));
	RenderCube ();
}

// RenderCube() Renders a 1x1 3D cube in NDC.
GLuint cubeVAO = 0;
GLuint cubeVBO = 0;
void RenderCube ()
{
	// Initialize (if necessary)
	if (cubeVAO == 0)
	{
		GLfloat vertices[] = {
			// Back face
			-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, // Bottom-left
			0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, // top-right
			0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, // bottom-right         
			0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f,  // top-right
			-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f,  // bottom-left
			-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f,// top-left
															  // Front face
			-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom-left
			0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f,  // bottom-right
			0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,  // top-right
			0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // top-right
			-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,  // top-left
			-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,  // bottom-left
															   // Left face
			-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right
			-0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-left
			-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f,  // bottom-left
			-0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-left
			-0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f,  // bottom-right
			-0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-right
															  // Right face
			0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, // top-left
			0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // bottom-right
			0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top-right         
			0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,  // bottom-right
			0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,  // top-left
			0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // bottom-left     
															 // Bottom face
			-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right
			0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, // top-left
			0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f,// bottom-left
			0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, // bottom-left
			-0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, // bottom-right
			-0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, // top-right
																// Top face
			-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,// top-left
			0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right
			0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, // top-right     
			0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom-right
			-0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,// top-left
			-0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f // bottom-left        
		};
		glGenVertexArrays (1, &cubeVAO);
		glGenBuffers (1, &cubeVBO);
		// Fill buffer
		glBindBuffer (GL_ARRAY_BUFFER, cubeVBO);
		glBufferData (GL_ARRAY_BUFFER, sizeof (vertices), vertices, GL_STATIC_DRAW);
		// Link vertex attributes
		glBindVertexArray (cubeVAO);
		glEnableVertexAttribArray (0);
		glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof (GLfloat), (GLvoid*)0);
		glEnableVertexAttribArray (1);
		glVertexAttribPointer (1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof (GLfloat), (GLvoid*)(3 * sizeof (GLfloat)));
		glEnableVertexAttribArray (2);
		glVertexAttribPointer (2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof (GLfloat), (GLvoid*)(6 * sizeof (GLfloat)));
		glBindBuffer (GL_ARRAY_BUFFER, 0);
		glBindVertexArray (0);
	}
	// Render Cube
	glBindVertexArray (cubeVAO);
	glDrawArrays (GL_TRIANGLES, 0, 36);
	glBindVertexArray (0);
}

// RenderQuad() Renders a 1x1 quad in NDC, best used for framebuffer color targets
// and post-processing effects.
GLuint quadVAO = 0;
GLuint quadVBO;
void RenderQuad ()
{
	if (quadVAO == 0)
	{
		GLfloat quadVertices[] = {
			// Positions        // Texture Coords
			-1.0f,  1.0f, 0.0f,  0.0f, 1.0f,
			-1.0f, -1.0f, 0.0f,  0.0f, 0.0f,
			1.0f,  1.0f, 0.0f,  1.0f, 1.0f,
			1.0f, -1.0f, 0.0f,  1.0f, 0.0f,
		};
		// Setup plane VAO
		glGenVertexArrays (1, &quadVAO);
		glGenBuffers (1, &quadVBO);
		glBindVertexArray (quadVAO);
		glBindBuffer (GL_ARRAY_BUFFER, quadVBO);
		glBufferData (GL_ARRAY_BUFFER, sizeof (quadVertices), &quadVertices, GL_STATIC_DRAW);
		glEnableVertexAttribArray (0);
		glVertexAttribPointer (0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof (GLfloat), (GLvoid*)0);
		glEnableVertexAttribArray (2);
		glVertexAttribPointer (2, 2, GL_FLOAT, GL_FALSE, 5 * sizeof (GLfloat), (GLvoid*)(3 * sizeof (GLfloat)));
	}
	glBindVertexArray (quadVAO);
	glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
	glBindVertexArray (0);
}

// Generates a texture that is suited for attachments to a framebuffer
GLuint generateAttachmentTexture (GLboolean depth, GLboolean stencil)
{
	// What enum to use?
	GLenum attachment_type;
	if (!depth && !stencil) {
		attachment_type = GL_RGB;
	}
	else if (depth && !stencil) {
		attachment_type = GL_DEPTH_COMPONENT;
	}
	else if (!depth && stencil) {
		attachment_type = GL_STENCIL_INDEX;
	}

	//Generate texture ID and load texture data 
	GLuint textureID;
	glGenTextures (1, &textureID);
	glBindTexture (GL_TEXTURE_2D, textureID);
	if (!depth && !stencil) {
		glTexImage2D (GL_TEXTURE_2D, 0, attachment_type, WIDTH, HEIGHT, 0, attachment_type, GL_UNSIGNED_BYTE, NULL);
	}
	else { // Using both a stencil and depth test, needs special format arguments
		glTexImage2D (GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, WIDTH, HEIGHT, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL);
	}
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glBindTexture (GL_TEXTURE_2D, 0);

	return textureID;
}

#pragma region "User input"

void error_callback (int error, const char* description)
{
	std::cout << (description) << std::endl;
}

void key_callback (GLFWwindow* window, int key, int scancode, int action, int mode)
{
	// When a user presses the escape key, we set the WindowShouldClose property to true,
	// closing the application

	if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
		glfwSetWindowShouldClose (window, GL_TRUE);
	}

	if (action == GLFW_PRESS) {
		keys[key] = true;
	}
	else if (action == GLFW_RELEASE) {
		keys[key] = false;
	}
}

void do_movement () {
	// Camera controls
	if (keys[GLFW_KEY_W]) {
		camera.ProcessKeyboard (Camera::FORWARD, deltaTime);
	}
	if (keys[GLFW_KEY_S]) {
		camera.ProcessKeyboard (Camera::BACKWARD, deltaTime);
	}
	if (keys[GLFW_KEY_A]) {
		camera.ProcessKeyboard (Camera::LEFT, deltaTime);
	}
	if (keys[GLFW_KEY_D]) {
		camera.ProcessKeyboard (Camera::RIGHT, deltaTime);
	}
	if (keys[GLFW_KEY_4]) {
		// wireframe: GL_LINE
		// GL_FILL
		glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);
	}
	if (keys[GLFW_KEY_5]) {
		glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
	}
}

bool firstMove = true;
void mouse_callback (GLFWwindow* window, double xpos, double ypos) {
	if (firstMove) {
		lastX = xpos;
		lastY = ypos;
		firstMove = false;
	}

	GLfloat xoffset = xpos - lastX;
	GLfloat yoffset = lastY - ypos;	// Reversed since y-coordinates range from bottom to top
	lastX = xpos;
	lastY = ypos;

	camera.ProcessMouseMovement (xoffset, yoffset);
}

void scroll_callback (GLFWwindow* window, double xoffset, double yoffset) {
	camera.ProcessMouseScroll (yoffset);
}

#pragma endregion


